// Output file stream based on C stdio library with some common functions
// derived from std::ofstream.

#ifndef STX_CUSTOM_OUTPUT_FILE_STREAM_HPP
#define STX_CUSTOM_OUTPUT_FILE_STREAM_HPP

#include <stx/base.hpp>

#include <stdio.h>

namespace stx {

class custom_output_file_stream
{
public:
    
    typedef long streamoff;
    
    typedef long streampos;
    
    typedef int streamsize;
    
    custom_output_file_stream()
    {
        fp = NULL;
    }
    
    custom_output_file_stream(const char* file_name)
    {
        open(file_name);
    }
    
    ~custom_output_file_stream()
    {
        close();
    }
    
    bool is_open()
    {
        return fp != NULL;
    }
    
    void open()
    {
        fp = fopen(file_name, "wb");
        if (!fp) {
            STX_THROW_SYSTEM_ERROR("fopen() failed, file name: " << file_name);
        }
    }
    
    void close()
    {
        if (fp) {
            int ret = fclose(fp);
            if (ret == EOF) {
                STX_THROW_SYSTEM_ERROR("fclose() failed");
            }
            fp = NULL;
        }
    }
    
    custom_output_file_stream& operator << (const short& val)
    {
        int ret = fprintf(fp, "%d", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const unsigned short& val)
    {
        int ret = fprintf(fp, "%u", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const int& val)
    {
        int ret = fprintf(fp, "%d", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const unsigned int& val)
    {
        int ret = fprintf(fp, "%u", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const long& val)
    {
        int ret = fprintf(fp, "%ld", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const unsigned long& val)
    {
        int ret = fprintf(fp, "%lu", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const float& val)
    {
        int ret = fprintf(fp, "%f", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const double& val)
    {
        int ret = fprintf(fp, "%f", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const long double& val)
    {
        int ret = fprintf(fp, "%Lf", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const void* val)
    {
        int ret = fprintf(fp, "%p", val);
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& operator << (const std::string& val)
    {
        int ret = fprintf(fp, "%s", val.c_str());
        if (ret < 0) {
            STX_THROW_SYSTEM_ERROR("fprintf() failed");
        }
        return *this;
    }
    
    friend custom_output_file_stream& operator << (custom_output_file_stream& out, const char val);
    
    friend custom_output_file_stream& operator << (custom_output_file_stream& out, const signed char val);
    
    friend custom_output_file_stream& operator << (custom_output_file_stream& out, const unsigned char val);
    
    friend custom_output_file_stream& operator << (custom_output_file_stream& out, const char* val);
    
    friend custom_output_file_stream& operator << (custom_output_file_stream& out, const signed char* val);
    
    friend custom_output_file_stream& operator << (custom_output_file_stream& out, const unsigned char* val);
    
    custom_output_file_stream& put(const char val)
    {
        int ret = fputc(val, c);
        if (ret == EOF) {
            STX_THROW_SYSTEM_ERROR("fputc() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& write(const char* s , streamsize n)
    {
        size_t ret = fwrite(s, n, 1, fp);
        if (ret != n) {
            STX_THROW_SYSTEM_ERROR("fwrite() failed");
        }
        return *this;
    }
    
    streampos tellp()
    {
        long ret = ftell(fp);
        if (ret == -1L) {
            STX_THROW_SYSTEM_ERROR("ftell() failed");
        }
        return ret;
    }
    
    custom_output_file_stream& seekp(streampos pos)
    {
        int ret = fseek(fp, pos, SEEK_SET);
        if (ret == -1) {
            STX_THROW_SYSTEM_ERROR("fseek() failed");
        }
        return *this;
    }
    
    custom_output_file_stream& flush()
    {
        int ret = fflush(fp);
        if (ret == EOF) {
            STX_THROW_SYSTEM_ERROR("fflush() failed");
        }
        return *this;
    }
    
private:
    
    FILE* fp;
};

inline custom_output_file_stream& operator << (custom_output_file_stream& out, char val)
{
    int ret = fprintf(out.fp, "%c", val);
    if (ret < 0) {
        STX_THROW_SYSTEM_ERROR("fprintf() failed");
    }
    return out;
}

inline custom_output_file_stream& operator << (custom_output_file_stream& out, signed char val)
{
    int ret = fprintf(out.fp, "%c", val);
    if (ret < 0) {
        STX_THROW_SYSTEM_ERROR("fprintf() failed");
    }
    return out;
}

inline custom_output_file_stream& operator << (custom_output_file_stream& out, unsigned char val)
{
    int ret = fprintf(out.fp, "%c", val);
    if (ret < 0) {
        STX_THROW_SYSTEM_ERROR("fprintf() failed");
    }
    return out;
}

inline custom_output_file_stream& operator << (custom_output_file_stream& out, const char* val)
{
    int ret = fprintf(out.fp, "%s", val);
    if (ret < 0) {
        STX_THROW_SYSTEM_ERROR("fprintf() failed");
    }
    return out;
}

inline custom_output_file_stream& operator << (custom_output_file_stream& out, const signed char* val)
{
    int ret = fprintf(out.fp, "%s", val);
    if (ret < 0) {
        STX_THROW_SYSTEM_ERROR("fprintf() failed");
    }
    return out;
}

inline custom_output_file_stream& operator << (custom_output_file_stream& out, const unsigned char* val)
{
    int ret = fprintf(out.fp, "%s", val);
    if (ret < 0) {
        STX_THROW_SYSTEM_ERROR("fprintf() failed");
    }
    return out;
}

} // namespace stx

#endif // STX_CUSTOM_OUTPUT_FILE_STREAM_HPP
